﻿using FCSChart.Axis;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace FCSChart.Series
{
    /// <summary>
    /// 散点图
    /// </summary>
    public class ScatterSeries : ISeries
    {
        #region property

        /// <summary>
        /// 散点的点的宽度
        /// </summary>
        public int PointLength
        {
            get { return (int)GetValue(PointLengthProperty); }
            set { SetValue(PointLengthProperty, value); }
        }
        public static readonly DependencyProperty PointLengthProperty = DependencyProperty.Register("PointLength", typeof(int), typeof(ScatterSeries), new PropertyMetadata(2, NeedRedrawingProperty_Changed));

        #endregion

        public ScatterSeries()
        {
            this.SeriesName = "Scatter";
        }

        internal override async void Drawing()
        {
            if (OwnerChart == null || OwnerChart.XSource == null || OwnerChart.YSource == null || OwnerChart.XAxis == null || OwnerChart.YAxis == null) return;
            if (CancelTokenSource != null)
            {
                CancelTokenSource.Cancel();
                CancelTokenSource.Dispose();
            }
            CancelTokenSource = new CancellationTokenSource();
            try
            {
                var temps = await GetGeometries(CancelTokenSource.Token, OwnerChart.XSource, OwnerChart.YSource, OwnerChart.XAxis, OwnerChart.YAxis, OwnerChart.Indexs, OwnerChart.XValueConverter, OwnerChart.YValueConverter, this.Fill, this.Stroke, this.GetGradientColor);
                if (temps != null)
                {
                    foreach (var temp in temps)
                    {
                        var g = Geometries.FirstOrDefault(p => p.Area == temp.Area);
                        if (g == null) Geometries.Add(temp);
                        else g.Stream = temp.Stream;
                    }
                    Drawing(Geometries);
                }
                if (CancelTokenSource != null)
                {
                    CancelTokenSource.Dispose();
                    CancelTokenSource = null;
                    System.Diagnostics.Debug.WriteLine("散点图结束生成矢量数据");
                }
            }
            catch (TaskCanceledException) { }
        }

        internal override Task<List<StreamColor>> GetGeometries(CancellationToken token, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter, Color fill, Color stroke, Func<long, long, Color> getGradientColor)
        {
            var pointcount = Math.Min(xSource.Count, ySource.Count);
            Func<double, ValueLocationConvertParam, double> xGetLocation = xAxis.GetValueLocation;
            Func<double, ValueLocationConvertParam, double> yGetLocation = yAxis.GetValueLocation;
            var xGetLocationParam = xAxis.GetConvertParam();
            var yGetLocationParam = yAxis.GetConvertParam();
            var maxX = xAxis.Max;
            var minX = xAxis.Min;
            var maxY = yAxis.Max;
            var minY = yAxis.Min;
            var pointLength = this.PointLength;
            var pointHalfLength = pointLength / 2;
            var maxDegreeOfParallelism = MaxDegreeOfParallelism;
            var areas = this.OwnerChart is ChartWithGraphicals tempchart && tempchart.AllAreas != null ? tempchart.AllAreas.ToArray() : null;
            var response = Task.Factory.StartNew(() =>
            {
                List<StreamColor> streams = new List<StreamColor>();

                #region 总的散点图
                ConcurrentDictionary<int, ConcurrentBag<int>> existed = new ConcurrentDictionary<int, ConcurrentBag<int>>();
                var streamGeometry = new StreamGeometry() { FillRule = FillRule.Nonzero };
                using (StreamGeometryContext sgc = streamGeometry.Open())
                {
                    if (indexs == null)
                    {
                        try
                        {
                            var result = Parallel.For(0, pointcount, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                            {
                                if (loop.IsStopped) return;
                                if (xSource == null || ySource == null || xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                                else AddValue(existed, i, xSource, ySource, maxX, minX, maxY, minY, xValueConverter, yValueConverter, xGetLocation, yGetLocation, xGetLocationParam, yGetLocationParam, pointLength);
                            });
                            if (!result.IsCompleted) return null;
                        }
                        catch (OperationCanceledException) { return null; }
                    }
                    else
                    {
                        try
                        {
                            var result = Parallel.ForEach(indexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                            {
                                if (loop.IsStopped) return;
                                if (xSource == null || ySource == null || xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                                else AddValue(existed, i, xSource, ySource, maxX, minX, maxY, minY, xValueConverter, yValueConverter, xGetLocation, yGetLocation, xGetLocationParam, yGetLocationParam, pointLength);
                            });
                            if (!result.IsCompleted) return null;
                        }
                        catch (OperationCanceledException) { return null; }
                    }
                    foreach (var item in existed)
                    {
                        var xtemp = item.Key;
                        foreach (var ytemp in item.Value)
                        {
                            var point = new Point(xtemp, ytemp);
                            sgc.BeginFigure(point, true, true);
                            sgc.PolyLineTo(new Point[] { new Point(xtemp + pointLength, ytemp), new Point(xtemp + pointLength, ytemp + pointLength), new Point(xtemp, ytemp + pointLength) }, false, false);
                        }
                    }
                    sgc.Close();
                }
                streamGeometry.Freeze();
                streams.Add(new StreamColor() { Stream = streamGeometry, Fill = fill, Stroke = stroke });
                #endregion

                #region 门的散点图
                if (areas != null)
                {
                    if (!AddAreasStream(areas, pointcount, xSource, ySource, indexs, maxX, minX, maxY, minY, xValueConverter, yValueConverter, xGetLocation, yGetLocation, xGetLocationParam, yGetLocationParam, pointLength, token, maxDegreeOfParallelism, streams))
                        return null;
                }
                #endregion

                return streams;
            }, token);
            return response;
        }

        private bool AddAreasStream(Graphical.GraphicalArea[] areas, int pointcount, IList xSource, IList ySource, IList<int> indexs, double maxX, double minX, double maxY, double minY, Func<object, double> xValueConverter, Func<object, double> yValueConverter, Func<double, ValueLocationConvertParam, double> xGetLocation, Func<double, ValueLocationConvertParam, double> yGetLocation, ValueLocationConvertParam xGetLocationParam, ValueLocationConvertParam yGetLocationParam, int pointLength, CancellationToken token, int maxDegreeOfParallelism, List<StreamColor> streams)
        {
            var pointHalfLength = pointLength / 2;
            foreach (var area in areas)
            {
                if (area == null || area.InsideIndexs == null || area.InsideIndexs.Count <= 0) continue;
                var maxindex = area.InsideIndexs.Max();
                if (pointcount <= maxindex) continue;
                ConcurrentDictionary<int, ConcurrentBag<int>> areaexisted = new ConcurrentDictionary<int, ConcurrentBag<int>>();
                var areastreamGeometry = new StreamGeometry() { FillRule = FillRule.Nonzero };
                using (StreamGeometryContext sgc = areastreamGeometry.Open())
                {
                    var tempindexs = area.InsideIndexs;
                    if (indexs != null) tempindexs = tempindexs.Intersect(indexs).ToArray();//取交集
                    try
                    {
                        var result = Parallel.ForEach(tempindexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                        {
                            if (loop.IsStopped) return;
                            if (xSource == null || ySource == null || xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                            else AddValue(areaexisted, i, xSource, ySource, maxX, minX, maxY, minY, xValueConverter, yValueConverter, xGetLocation, yGetLocation, xGetLocationParam, yGetLocationParam, pointLength);
                        });
                        if (!result.IsCompleted) return false;
                    }
                    catch (OperationCanceledException) { return false; }
                    foreach (var item in areaexisted)
                    {
                        var xtemp = item.Key;
                        foreach (var ytemp in item.Value)
                        {
                            var point = new Point(xtemp, ytemp);
                            sgc.BeginFigure(point, true, true);
                            sgc.PolyLineTo(new Point[] { new Point(xtemp + pointLength, ytemp), new Point(xtemp + pointLength, ytemp + pointLength), new Point(xtemp, ytemp + pointLength) }, false, false);
                        }
                    }
                    sgc.Close();
                }
                areastreamGeometry.Freeze();
                streams.Add(new StreamColor() { Stream = areastreamGeometry, Fill = area.DisplayColor, Stroke = area.DisplayColor, Area = area });
            }
            return true;
        }

        private void AddValue(ConcurrentDictionary<int, ConcurrentBag<int>> existed, int i, IList xSource, IList ySource, double maxX, double minX, double maxY, double minY, Func<object, double> xValueConverter, Func<object, double> yValueConverter, Func<double, ValueLocationConvertParam, double> xGetLocation, Func<double, ValueLocationConvertParam, double> yGetLocation, ValueLocationConvertParam xGetLocationParam, ValueLocationConvertParam yGetLocationParam, int pointLength)
        {
            if (xSource != null || ySource != null)
            {
                var valueX = xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]);
                var valueY = yValueConverter == null ? Convert.ToDouble(ySource[i]) : yValueConverter(ySource[i]);
                //if (valueX > maxX || valueX < minX || valueY > maxY || valueY < minY) return;
                if (valueX > maxX) valueX = maxX; else if (valueX < minX) valueX = minX;
                if (valueY > maxY) valueY = maxY; else if (valueY < minY) valueY = minY;
                var xvalue = xGetLocation(valueX, xGetLocationParam);
                var yvalue = yGetLocation(valueY, yGetLocationParam);
                if (double.IsNaN(xvalue) || double.IsInfinity(xvalue) || double.IsNaN(yvalue) || double.IsInfinity(yvalue)) return;
                //var xtemp = Convert.ToInt32(xvalue);
                //var ytemp = Convert.ToInt32(yvalue);
                var xtemp = Math.Abs(xvalue % pointLength) > 0 ? ((Convert.ToInt32(xvalue) / pointLength - 1) * pointLength) : Convert.ToInt32(xvalue);
                var ytemp = Math.Abs(yvalue % pointLength) > 0 ? ((Convert.ToInt32(yvalue) / pointLength - 1) * pointLength) : Convert.ToInt32(yvalue);
                if (existed.ContainsKey(xtemp) && existed[xtemp] != null && existed[xtemp].Contains(ytemp)) return;
                if (existed.ContainsKey(xtemp) && existed[xtemp] != null) existed[xtemp].Add(ytemp);
                else existed[xtemp] = new ConcurrentBag<int>() { ytemp };
            }
        }

        #region 区域显示
        /// <summary>
        /// 添加区域
        /// </summary>
        /// <param name="area"></param>
        internal override void AddArea(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || Geometries.Any(p => p.Area == area)) return;
            var stream = GetAreaStream(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
            if (stream != null)
            {
                lock (Geometries)
                {
                    if (Geometries.Any(p => p.Area == area)) return;
                    Geometries.Add(new StreamColor() { Stream = stream, Area = area, Fill = area.DisplayColor, Stroke = area.DisplayColor });
                }
                Drawing(Geometries);
            }
        }
        /// <summary>
        /// 删除区域
        /// </summary>
        /// <param name="area"></param>
        internal override void RemoveArea(Graphical.GraphicalArea area)
        {
            if (Geometries != null)
            {
                var temp = Geometries.FirstOrDefault(p => p.Area == area);
                if (temp != null && temp.Stream != null)
                {
                    Geometries.Remove(temp);
                    Drawing(Geometries);
                }
            }
        }
        /// <summary>
        /// 更新区域显示颜色
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaDisplayColor(Graphical.GraphicalArea area)
        {
            if (Geometries != null && area != null)
            {
                var temp = Geometries.FirstOrDefault(p => p.Area == area);
                if (temp != null && temp.Stream != null)
                {
                    temp.Fill = temp.Stroke = area.DisplayColor;
                    Drawing(Geometries);
                }
            }
        }
        /// <summary>
        /// 更新区域索引
        /// </summary>
        /// <param name="area"></param>
        internal override void UpdateAreaIndexs(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || Geometries == null) return;
            var geometries = Geometries.FirstOrDefault(p => p.Area == area);
            if (geometries == null)
            {
                AddArea(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
            }
            else
            {
                var stream = GetAreaStream(area, xSource, ySource, xAxis, yAxis, indexs, xValueConverter, yValueConverter);
                if (stream != null) geometries.Stream = stream;
                else lock (Geometries) Geometries.Remove(geometries);
                Drawing(Geometries);
            }
        }

        private StreamGeometry GetAreaStream(Graphical.GraphicalArea area, IList xSource, IList ySource, IAxis xAxis, IAxis yAxis, IList<int> indexs, Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (area == null || area.InsideIndexs == null || area.InsideIndexs.Count <= 0 || xSource == null || ySource == null || xSource.Count <= 0 || ySource.Count <= 0) return null;
            Func<double, ValueLocationConvertParam, double> xGetLocation = xAxis.GetValueLocation;
            Func<double, ValueLocationConvertParam, double> yGetLocation = yAxis.GetValueLocation;
            var xGetLocationParam = xAxis.GetConvertParam();
            var yGetLocationParam = yAxis.GetConvertParam();
            var maxX = xAxis.Max;
            var minX = xAxis.Min;
            var maxY = yAxis.Max;
            var minY = yAxis.Min;
            var pointLength = this.PointLength;
            var pointHalfLength = pointLength / 2;
            var maxDegreeOfParallelism = MaxDegreeOfParallelism;
            ConcurrentDictionary<int, ConcurrentBag<int>> areaexisted = new ConcurrentDictionary<int, ConcurrentBag<int>>();
            var areastreamGeometry = new StreamGeometry() { FillRule = FillRule.Nonzero };
            using (StreamGeometryContext sgc = areastreamGeometry.Open())
            {
                var tempindexs = area.InsideIndexs;
                if (indexs != null) tempindexs = tempindexs.Intersect(indexs).ToArray();//取交集
                try
                {
                    var result = Parallel.ForEach(tempindexs, new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                    {
                        if (loop.IsStopped) return;
                        if (xSource == null || ySource == null || xSource.Count <= i || ySource.Count <= i) loop.Stop();
                        else AddValue(areaexisted, i, xSource, ySource, maxX, minX, maxY, minY, xValueConverter, yValueConverter, xGetLocation, yGetLocation, xGetLocationParam, yGetLocationParam, pointLength);
                    });
                    if (!result.IsCompleted) return null;
                }
                catch (OperationCanceledException) { return null; }
                foreach (var item in areaexisted)
                {
                    var xtemp = item.Key;
                    foreach (var ytemp in item.Value)
                    {
                        var point = new Point(xtemp, ytemp);
                        sgc.BeginFigure(point, true, true);
                        sgc.PolyLineTo(new Point[] { new Point(xtemp + pointLength, ytemp), new Point(xtemp + pointLength, ytemp + pointLength), new Point(xtemp, ytemp + pointLength) }, false, false);
                    }
                }
                sgc.Close();
            }
            areastreamGeometry.Freeze();
            return areastreamGeometry;
        }
        #endregion
    }
}
